// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace SnapObjects.Data
{
    public abstract class SqlTokenizer : SqlTokenizerBase, ISqlTokenizer
    {
        protected ISqlGenerationHelper _helper;
        protected IList<string> _tokens;
        protected int _index = -1;

        public SqlTokenizer(string sqlText, ISqlGenerationHelper helper) : base(sqlText)
        {
            _helper = helper;
            _tokens = this.GetTokens();
        }

        public SqlToken Current { get; set; }

        object IEnumerator.Current => this.Current;

        public IEnumerator<SqlToken> GetEnumerator()
        {
            return this;
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        public void Reset()
        {
            _index = -1;
            this.Current = null;
        }

        public bool MoveNext()
        {
            _index++;

            if (_index <= _tokens.Count - 1)
            {
                var text = _tokens[_index];

                this.Current = this.ParseToken(text);

                return true;
            }
            else
            {
                // this.Reset();
                this.Current = null;
            }

            return false;
        }

        protected virtual bool IsKeyword(string text, out SqlKeywordToken keyword)
        {
            if (SqlKeywords.IsKeyword(text))
            {
                keyword = new SqlKeywordToken(text);

                return true;
            }

            keyword = null;

            return false;
        }

        protected virtual bool IsCharacter(string text, out SqlCharacterToken character)
        {
            if (SqlCharacters.IsCharacter(text))
            {
                character = new SqlCharacterToken(text);

                return true;
            }

            character = null;

            return false;
        }

        protected virtual bool IsParameter(string text)
        {
            return text.Length > 1
                && Equals(text[0], _helper.ParameterIdentifier)
                && !Equals(text[1], _helper.ParameterIdentifier);
        }

        protected virtual bool IsGlobalVariable(string text)
        {
            return text.Length > 2
                && Equals(text[0], _helper.ParameterIdentifier)
                && Equals(text[1], _helper.ParameterIdentifier)
                && !Equals(text[2], _helper.ParameterIdentifier);
        }

        protected virtual SqlToken ParseToken(string text)
        {
            SqlToken token;

            if (Char.IsWhiteSpace(text[0]))
            {
                token = SqlWhitespaceToken.Whitespace;
            }
            else if (text.StartsWith("\'") || text.StartsWith("N\'")
                || (text.StartsWith("[") && text.EndsWith("]")))
            {
                token = new SqlStringToken(text);
            }
            else if (text.StartsWith("/*") ||
                text.StartsWith("--"))
            {
                token = new SqlCommentToken(text);
            }
            else if (text.StartsWith("//"))
            {
                text = text.Remove(0, 2);
                text = "--" + text;

                token = new SqlCommentToken(text);
            }
            else if (text[0] == '$')
            {
                if (text.Length > 1 && Char.IsLetter(text[1]))
                {
                    token = new SqlIdentifierToken(text);
                }
                else
                {
                    token = new SqlMoneyToken(text);
                }
            }
            else if (this.IsCurrencySymbol(text[0]))
            {
                token = new SqlMoneyToken(text);
            }
            else if (text.StartsWith("0x", StringComparison.InvariantCultureIgnoreCase))
            {
                token = new SqlBinaryToken(text);
            }
            else if (Char.IsDigit(text[0]) || (text[0] == '.' && text.Length > 1 && Char.IsDigit(text[1])))
            {
                token = new SqlNumericToken(text);
            }
            else if (_operators.Contains(text[0]) || text.StartsWith("::"))
            {
                token = new SqlOperatorToken(text);
            }
            else if (this.IsCharacter(text, out var character))
            {
                token = character;
            }
            else if (this.IsKeyword(text, out var keyword))
            {
                if (this.CheckKeywordIegality(text))
                {
                    token = keyword;
                }
                else
                {
                    token = new SqlIdentifierToken(text);
                }
            }
            else if (this.IsGlobalVariable(text))
            {
                token = new SqlGlobalVariableToken(text);
            }
            else if (this.IsParameter(text))
            {
                token = new SqlParameterToken(text.Substring(1));
            }
            else
            {
                token = new SqlIdentifierToken(text);
            }

            return token;
        }

        private readonly string[] prevChars = { " ", "\r", "\n", ")", "(", ",", "{", "=" };

        private readonly string[] nextChars = { " ", "(", ")", "\r", "\n", ",", ";" };

        protected virtual bool CheckKeywordIegality(string text)
        {
            if (text.Equals("null", StringComparison.OrdinalIgnoreCase))
            {
                return true;
            }

            var prevIndex = _index - 1;
            var nextIndex = _index + 1;

            string prev = prevIndex > -1 ? _tokens[prevIndex] : String.Empty,
                next = nextIndex < _tokens.Count ? _tokens[nextIndex] : String.Empty;

            if (prev.Equals(".") || next.Equals("."))
            {
                return false;
            }

            if (prev == String.Empty || next == String.Empty)
            {
                return true;
            }

            if ((prevChars.Contains(prev) || String.IsNullOrWhiteSpace(prev))
                && (nextChars.Contains(next) || String.IsNullOrWhiteSpace(next)))
            {
                return true;
            }

            if (prev.StartsWith("'") && prev.EndsWith("'"))
            {
                return true;
            }

            if (String.IsNullOrWhiteSpace(prev) && next.StartsWith("\""))
            {
                return true;
            }

            if (prev.EndsWith("*/") || prev.EndsWith("\r\n"))
            {
                return true;
            }

            return false;
        }
    }
}
